﻿using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace Ecobee.Net
{
	/// <summary>
	/// Represents the Implementation of IHttp to communicate to the ecobee
	/// Web API.
	/// </summary>
	public class Http : IHttp
	{
		/// <summary>
		/// This event is fired when a request is started.
		/// </summary>
		public event EventHandler RequestStarted = null;

		/// <summary>
		/// This event is fired when a request has completed.
		/// </summary>
		public event EventHandler RequestCompleted = null;

		private Api _api = null;
		private HttpClient _httpClient = null;

		public Http(Api api)
		{
			this.Api = api;
		}

		/// <summary>
		/// Gets an instance of the System.Net.Http.HttpClient object
		/// used for making calls to the ecobee API.
		/// </summary>
		public HttpClient ApiClient
		{
			get
			{
				return _httpClient;
			}
			private set
			{
				_httpClient = value;
			}
		}

		/// <summary>
		/// Gets an reference of the active Api instance.
		/// </summary>
		protected Api Api
		{
			get
			{
				return _api;
			}
			set
			{
				_api = value;
			}
		}

		/// <summary>
		/// Performs a GET request to the ecobee API using the specified address.
		/// </summary>
		/// <param name="address">The URL of the request.</param>
		/// <param name="allowTokenRefresh">Determines if an expired token will be refreshed automatically.</param>
		/// <returns>Returns an Ecobee.Net.ApiResult object containing the
		/// result of the API call.</returns>
		public async Task<ApiResult> Get(string address, bool allowTokenRefresh = true)
		{
			ApiResult returnValue = new ApiResult();

			// ***
			// *** Create an Http client for the request
			// ***
			this.ApiClient = await this.Create(allowTokenRefresh);

			try
			{
				// ***
				// *** Fire the request started event
				// ***
				this.OnRequestStarted();

				// ***
				// *** Make the call to the remote host
				// ***
				string formattedAddress = Api.BuildUri(address, this.Api.DefaultParameters(this.Api.Access));
				var response = await this.ApiClient.GetAsync(formattedAddress);

				// ***
				// *** Process the response
				// ***
				await returnValue.ProcessResponse(response);
			}
			finally
			{
				// ***
				// *** Fire the request completed event
				// ***
				this.OnRequestCompleted();
			}

			return returnValue;
		}

		/// <summary>
		/// Performs a POST request to the ecobee API using the specified address.
		/// </summary>
		/// <param name="address">The URL of the request.</param>
		/// <param name="content">Content that will be posted during the request.</param>
		/// <param name="allowTokenRefresh">Determines if an expired token will be refreshed automatically.</param>
		/// <returns>Returns an Ecobee.Net.ApiResult object containing the
		/// result of the API call.</returns>
		public async Task<ApiResult> Post(string address, ApiObject content = null, bool allowTokenRefresh = true)
		{
			ApiResult returnValue = new ApiResult();

			// ***
			// *** Create an Http client for the request
			// ***
			this.ApiClient = await this.Create(allowTokenRefresh);

			// ***
			// *** Serialize the object to JSON
			// ***
			HttpContent httpContent = null;

			if (content != null)
			{
				string json = content.Serialize(Formatting.Indented);
				httpContent = new StringContent(json, UTF8Encoding.UTF8, "application/json");
			}

			try
			{
				// ***
				// *** Fire the request started event
				// ***
				this.OnRequestStarted();

				// ***
				// *** Make the call to the remote host
				// ***
				string fullAddress = this.Api.BuildUri(address, this.Api.DefaultParameters(this.Api.Access));

				HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, fullAddress);
				message.Headers.ExpectContinue = false;
				message.Content = httpContent;
				HttpResponseMessage response = await this.ApiClient.SendAsync(message);

				// ***
				// *** Process the response
				// ***
				await returnValue.ProcessResponse(response);
			}
			finally
			{
				// ***
				// *** Fire the request completed event
				// ***
				this.OnRequestCompleted();

				if (httpContent != null)
				{
					httpContent.Dispose();
				}
			}

			return returnValue;
		}

		/// <summary>
		/// Creates an instance of the System.Net.Http.HttpClient object specifically
		/// setup to make a call to the ecobee API.
		/// </summary>
		/// <param name="allowTokenRefresh">Determines if an expired token will be refreshed automatically.</param>
		/// <returns>Returns a System.Net.Http.HttpClient object ready for use.</returns>
		internal async Task<HttpClient> Create(bool allowTokenRefresh = true)
		{
			// ***
			// *** Create an Http client for the request
			// ***
			HttpClient returnValue = new HttpClient();
			returnValue.Timeout = this.Api.Timeout;

			// ***
			// *** Add the headers
			// ***
			returnValue.DefaultRequestHeaders.Add("Accept", "application/json;charset=UTF-8");
			returnValue.DefaultRequestHeaders.Add("UserAgent", this.Api.ApplicationName);

			// ***
			// *** Modify the request if an Access object is given
			// ***
			if (this.Api.Access.IsValid)
			{
				if (allowTokenRefresh && DateTime.Now > this.Api.Access.ExpiresAt)
				{
					// ***
					// *** The ecobee access token must be refreshed
					// ***
					bool result = await this.Api.Access.Refresh();
				}

				// ***
				// *** Add the token to the address
				// ***
				if (!string.IsNullOrEmpty(this.Api.Access.AccessToken.Identifier))
				{
					// ***
					// *** Add the Authorization header
					// ***
					returnValue.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(this.Api.Access.TokenType, this.Api.Access.AccessToken.Identifier);
				}
			}

			return returnValue;
		}

		/// <summary>
		/// Cancels the current HTTP operation.
		/// </summary>
		public void Cancel()
		{
			if (this.ApiClient != null)
			{
				this.ApiClient.CancelPendingRequests();
			}
		}

		/// <summary>
		/// Override to perform an action when a request is started.
		/// </summary>
		protected virtual void OnRequestStarted()
		{
			if (this.RequestStarted != null)
			{
				this.RequestStarted(this, new EventArgs());
			}
		}

		/// <summary>
		/// Override to perform an action when a request has completed.
		/// </summary>
		protected virtual void OnRequestCompleted()
		{
			if (this.RequestCompleted != null)
			{
				this.RequestCompleted(this, new EventArgs());
			}
		}
	}
}
